package com.quanyan.common.interceptor;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.quanyan.api.APIResponse;
import com.quanyan.api.enums.ApiResponseEnum;
import com.quanyan.common.exception.BizException;
import com.quanyan.common.utils.GsonUtils;
import com.quanyan.common.utils.ObjectUtil;
import com.quanyan.common.utils.ServletUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 校验参数有效性 记录请求参数, 返回数值.
 *
 * @author songzj
 * @date 16/4/14-18:51
 */
@Aspect
@Component
public class ParameterInvalidInterceptor {

    private static Logger logger = LoggerFactory.getLogger(ParameterInvalidInterceptor.class);

    private ExecutorService executorService = Executors.newSingleThreadExecutor();


    @Pointcut("execution(* com.quanyan..web..*Controller.*(..))")
    public void controller() {
    }

    @Pointcut("execution(* com.quanyan..service.*Service.*(..))")
    public void service() {
    }

    @Around(value = "service() && @annotation(javax.validation.Valid)")
    public Object profileService(ProceedingJoinPoint poj) throws Throwable {
        try {
            parameterValidation(poj.getArgs());
        } catch (BizException e) {
            String errDesc = GsonUtils.toJson(e.getErrorData()).replaceAll("\"", "'");
            logger.warn("请求参数不符合规范 : " + errDesc);
            if (e.getApiResponseEnum() != null) {
                return APIResponse.returnFail(e.getApiResponseEnum(), errDesc, e.getErrorData());
            }
            return APIResponse.returnFail(e.getErrorData(), e.getMessage());
        }

        Object result = null; //处理业务流程
        try {
            result = poj.proceed();
            log(poj.getSignature(), poj.getArgs(), result, true); //打印结果
        } catch (Exception e) {
            logger.error("Exception Hanlder : " + ObjectUtil.getFullStackTraceToString(e));
            Object[] args = poj.getArgs();  //获取方法注解
            Class[] argClass = new Class[args.length];
            for (int i = 0; i < args.length; i++) {
                argClass[i] = args[i].getClass();
            }

            Method method = poj.getTarget().getClass().getDeclaredMethod(
                    MethodSignature.class.cast(poj.getSignature()).getMethod().getName(), argClass);
            if (null != method.getAnnotation(Transactional.class)) {
                //方法上有Transactional注解就回滚
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//回滚事务
            }
//            MethodSignature methodSignature = (MethodSignature) poj.getSignature();
            //Method method = methodSignature.getMethod();
            Class aClass = method.getReturnType();
            if (aClass.equals(APIResponse.class)) {
                if (e instanceof BizException) {
                    BizException bizException = (BizException) e;
                    if (bizException.getApiResponseEnum() != null) {
                        log(poj.getSignature(), poj.getArgs(), APIResponse.returnFail(bizException.getApiResponseEnum()), true);
                        return APIResponse.returnFail(bizException.getApiResponseEnum());
                    } else {
                        log(poj.getSignature(), poj.getArgs(), APIResponse.returnFail(bizException.getMessage()), true);
                        return APIResponse.returnFail(bizException.getMessage());
                    }

                } else {
                    log(poj.getSignature(), poj.getArgs(), APIResponse.returnFail(ApiResponseEnum.INTERNAL_ERROR), true);
                    return APIResponse.returnFail(ApiResponseEnum.INTERNAL_ERROR);
                }
            }
        }
        return result;

    }

    @Around(value = "controller()")
    public Object profile(ProceedingJoinPoint poj) throws Throwable {
        try {
            parameterProcess(poj.getArgs()); //参数校验
        } catch (BizException e) {
            log(poj.getSignature(), poj.getArgs(), e.getErrorData(), false); //打印结果
            throw e;
        }
        try {
            Object result = poj.proceed(); //处理业务流程
            log(poj.getSignature(), poj.getArgs(), result, false); //打印结果
            return result;
        } catch (Exception e) {
            log(poj.getSignature(), poj.getArgs(), e.getMessage(), false); //打印结果
            throw e;
        }

    }

    private void parameterValidation(Object[] args) throws BizException {
        for (Object arg : args) {

            ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
            Validator validator = validatorFactory.getValidator();
            Set<ConstraintViolation<Object>> constraintViolationSet = validator.validate(arg);
            if (constraintViolationSet != null && constraintViolationSet.size() > 0) {
                Map<String, String> map = new HashMap<String, String>();
                for (ConstraintViolation<Object> objectConstraintViolation : constraintViolationSet) {
                    map.put(objectConstraintViolation.getPropertyPath().toString(), objectConstraintViolation.getMessage());
                }
                throw new BizException(ApiResponseEnum.PARAMETER_INVALID, map);
            }
        }
    }

    private void parameterProcess(Object[] args) throws BizException {
        for (Object arg : args) {
            if (arg instanceof BindingResult) {
                BindingResult result = (BindingResult) arg;
                if (result.hasErrors()) {
                    List<FieldError> err = result.getFieldErrors();
                    Map<String, String> map = new HashMap<String, String>();
                    for (FieldError fieldError : err) {
                        map.put(fieldError.getField(), fieldError.getDefaultMessage());
                    }
                    throw new BizException(ApiResponseEnum.PARAMETER_INVALID, map);
                }
            }
        }
    }

    private void log(final Signature signature, Object[] args, final Object result, boolean isService) {
        try {
            Object[] tmpArgs = new Object[args.length];
            int i = 0;
            for (Object o : args) {
                if (o instanceof HttpServletRequest
                        || o instanceof HttpServletResponse
                        || o instanceof BindingResult) {
                    continue;
                }
                tmpArgs[i++] = o;
            }
            if (!isService) {
                HttpServletRequest httpServletRequest = ServletUtils.getHttpServletRequest();
                logger.warn("[requestIP     ] : {}", ServletUtils.getClientIpAddress(httpServletRequest));
                logger.warn("[requestURI    ] : {}", httpServletRequest.getRequestURI());
                logger.warn("[requestParam  ] : {}", JSON.toJSONString(httpServletRequest.getParameterMap()));
            }
            logger.warn("[callMethod    ] : {}", signature.toString());
            logger.warn("[args          ] : {}", JSONArray.toJSONString(tmpArgs));
            logger.warn("[result        ] : {}", result == null ? "" : result.toString());

            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    //TODO send message to kafka or to do other things.
                }
            });
        } catch (Exception e) {
            logger.error("log exceptioon {}", e);
        }
    }


}
